package cn.liuxiany.alibaba.gateway.config;

import cn.liuxiany.alibaba.gateway.handler.GlobalExceptionHandler;
import com.fasterxml.jackson.databind.ObjectMapper;
import io.github.resilience4j.circuitbreaker.CircuitBreakerConfig;
import io.github.resilience4j.timelimiter.TimeLimiterConfig;
import org.springframework.cloud.circuitbreaker.resilience4j.ReactiveResilience4JCircuitBreakerFactory;
import org.springframework.cloud.circuitbreaker.resilience4j.Resilience4JConfigBuilder;
import org.springframework.cloud.client.circuitbreaker.Customizer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.web.cors.reactive.CorsUtils;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.server.WebFilter;
import org.springframework.web.server.WebFilterChain;
import reactor.core.publisher.Mono;

import java.time.Duration;

/**
 * 网关配置类
 *
 * @author liuxiany
 * @date 2023/09/27
 */
@Configuration
public class GatewayConfig {

    /**
     * 跨域配置
     *
     * @return
     */
    @Bean
    public WebFilter corsFilter() {
        return (ServerWebExchange ctx, WebFilterChain chain) -> {
            ServerHttpRequest request = ctx.getRequest();

            if (CorsUtils.isCorsRequest(request)) {
                HttpHeaders requestHeaders = request.getHeaders();
                HttpMethod requestMethod = requestHeaders.getAccessControlRequestMethod();

                ServerHttpResponse response = ctx.getResponse();
                HttpHeaders responseHeaders = response.getHeaders();

                responseHeaders.add(HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN, requestHeaders.getOrigin());
                responseHeaders.addAll(HttpHeaders.ACCESS_CONTROL_ALLOW_HEADERS, requestHeaders.getAccessControlRequestHeaders());

                if (requestMethod != null) {
                    responseHeaders.add(HttpHeaders.ACCESS_CONTROL_ALLOW_METHODS, requestMethod.name());
                }

                responseHeaders.add(HttpHeaders.ACCESS_CONTROL_ALLOW_CREDENTIALS, "true");
                responseHeaders.add(HttpHeaders.ACCESS_CONTROL_EXPOSE_HEADERS, "*");

                if (request.getMethod() == HttpMethod.OPTIONS) {
                    response.setStatusCode(HttpStatus.OK);
                    return Mono.empty();
                }
            }

            return chain.filter(ctx);
        };
    }

    /**
     * 全局异常处理
     *
     * @param objectMapper
     * @return
     */
    @Bean
    public GlobalExceptionHandler globalExceptionHandler(ObjectMapper objectMapper) {
        return new GlobalExceptionHandler(objectMapper);
    }

    /**
     * 熔断器配置
     * 会进入fallbackController
     * 与yml配置二选一
     * @return
     */
/*    @Bean
    public Customizer<ReactiveResilience4JCircuitBreakerFactory> defaultCustomizer() {
        return factory -> factory.configureDefault(id -> new Resilience4JConfigBuilder(id)
//                .circuitBreakerConfig(CircuitBreakerConfig.ofDefaults())
                .circuitBreakerConfig(circuitBreakerConfig)
                // 10s超时，进入fallback
                .timeLimiterConfig(TimeLimiterConfig.custom().timeoutDuration(Duration.ofSeconds(30)).build())
                .build());
    }*/

    /**
     * 自定义熔断器规则
     */
    CircuitBreakerConfig circuitBreakerConfig = CircuitBreakerConfig.custom()
            // 滑动窗口的类型为时间窗口。COUNT_BASED：根据数量计算，slidingWindowSize为次数。TIME_BASED：根据时间计算，slidingWindowSize为秒数
            .slidingWindowType(CircuitBreakerConfig.SlidingWindowType.TIME_BASED)
            .slidingWindowSize(1) // 时间窗口的大小为60秒
            .minimumNumberOfCalls(1) // 在单位时间窗口内最少需要5次调用才能开始进行统计计算. 计算错误率的最小请求数，不足最小调用次数不会触发任何变化。
            .failureRateThreshold(10) // 在单位时间窗口内调用失败率达到50%后会启动断路器
//            .slowCallDurationThreshold(Duration.ofSeconds(30)) // 慢调用阀值，请求执行的时间大于该值时会标记为慢调用
//            .slowCallRateThreshold(100) // 慢调用熔断阀值，当慢调用率达到或高于该值时，进入open状态
            .enableAutomaticTransitionFromOpenToHalfOpen() // 允许断路器自动由打开状态转换为半开状态, 默认false，一定时间之后进入half-open。true则通过接口调用。
            .waitDurationInOpenState(Duration.ofSeconds(10)) // 断路器打开状态转换为半开状态需要等待60秒
            .permittedNumberOfCallsInHalfOpenState(2) // 在半开状态下允许进行正常调用的次数，根据此数量的请求计算失败率。低于则close,高于则open。
            .recordExceptions(java.lang.Throwable.class) // 所有异常都当作失败来处理
//            .ignoreExceptions(java.lang.IllegalArgumentException.class) // 不会被计为失败的异常集合，优先级高于recordExceptions
            .build();
}
